package ch.ethz.fcl.metrobuzz.data.interchange;

import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;

import ch.ethz.fcl.metrobuzz.algorithm.list.ListOperation;
import ch.ethz.fcl.metrobuzz.data.MBData;
import ch.ethz.fcl.metrobuzz.data.roadnetwork.RoadNetwork;
import ch.ethz.fcl.metrobuzz.data.scene.basemap.Edge;
import ch.ethz.fcl.metrobuzz.data.scene.basemap.Node;
import ch.ethz.fcl.metrobuzz.data.singapore.SGInterchange;
import ch.ethz.fcl.metrobuzz.data.singapore.SGMRTStations;
import ch.ethz.fcl.metrobuzz.tools.MBTools;

/**
 * write junction information and interchange to files
 * 
 * @author Zeng Wei
 * @email zeng@arch.ethz.ch
 * 
 */
public class JunctionWriter {
	private MBData data;
	private float distThre = 0.002f; // threshold of distance between two nodes
										// belonging to a same junction node

	private String distributionFile;

	public JunctionWriter(MBData data) {
		this.data = data;
	}

	public void setDistributionFile(String file) {
		this.distributionFile = file;
	}

	public ArrayList<Junction> createJunctionNodes(SGInterchange interchange) {
		ArrayList<Junction> jns = new ArrayList<Junction>();

		RoadNetwork rn = data.getRoadNetwork();

		ArrayList<Node> junctionNodes = rn.getJunctionNodes();

		float[][] dist = rn.getJunctionDistance();
		boolean[] added = new boolean[junctionNodes.size()];

		for (int i = 0; i < junctionNodes.size(); i++) {
			if (!added[i]
					&& !SGMRTStations
							.isMRTStation(junctionNodes.get(i).getId())) {
				Junction junction = new Junction(jns.size());
				junction.addNode(junctionNodes.get(i));

				interchange.mapNodeJunction(junctionNodes.get(i), junction);

				for (int j = 0; j < dist[i].length; j++) {
					if (dist[i][j] <= distThre && i != j && !added[j]) {
						junction.addNode(junctionNodes.get(j));
						added[j] = true;

						interchange.mapNodeJunction(junctionNodes.get(j),
								junction);

						expandJunctionNodes(interchange, added, dist,
								junctionNodes, distThre, j, junction);
					}
				}
				jns.add(junction);
			} else if (!added[i]
					&& SGMRTStations.isMRTStation(junctionNodes.get(i).getId())) {
				Junction junction = new Junction(jns.size());
				junction.addNode(junctionNodes.get(i));
				interchange.mapNodeJunction(junctionNodes.get(i), junction);
				jns.add(junction);
			}
		}

		for (Node start : rn.getStartNodes()) {
			Junction junction = new Junction(jns.size());
			junction.addNode(start);
			jns.add(junction);
		}

		for (Junction jn : jns) {
			jn.initEdges(interchange, data.getRoadNetwork());
		}

		for (Junction jn : jns)
			jn.initEdgesColor(interchange, data.getRoadNetwork());

		for (Junction jn : jns)
			jn.groupEdges();

		for (Junction jn : jns)
			jn.initCircos(interchange, data.getRoadNetwork());

		return jns;
	}

	private void expandJunctionNodes(SGInterchange interchange,
			boolean[] added, float[][] dist, ArrayList<Node> junctionNodes,
			float distThre, int index, Junction junction) {
		for (int j = 0; j < dist[index].length; j++) {
			if (dist[index][j] <= distThre
					&& index != j
					&& !added[j]
					&& !SGMRTStations
							.isMRTStation(junctionNodes.get(j).getId())) {
				junction.addNode(junctionNodes.get(j));
				added[j] = true;

				interchange.mapNodeJunction(junctionNodes.get(j), junction);
				expandJunctionNodes(interchange, added, dist, junctionNodes,
						distThre, j, junction);
			}
		}
	}

	public void writeJunctionNodes(String file_name, SGInterchange interchange) {
		try {
			FileWriter fw = new FileWriter(file_name);
			for (Junction j : interchange.getJunctions()) {
				fw.write("index:\t" + j.getIndex() + "\t");
				fw.write("x:\t" + j.getCircosCentroid().getX() + "\t");
				fw.write("y:\t" + j.getCircosCentroid().getY() + "\t");
				fw.write("color:\t" + j.getColorIndex() + "\n");

				fw.write("IncomingEdges:\t");
				for (Edge e : j.incomingEdges)
					fw.write(e.getId() + "\t");
				fw.write("\n");

				fw.write("OutgoingEdges:\t");
				for (Edge e : j.outgoingEdges)
					fw.write(e.getId() + "\t");
				fw.write("\n");

				fw.write("Nodes:\t");
				if (j.getNodes() != null)
					for (Node n : j.getNodes())
						fw.write(n.getId() + "\t");
				fw.write("\n");

				Object[] colors = j.getColorEdgesMap().keySet().toArray();
				MBTools.debug("junction " + j.index + " colors length "
						+ colors.length, true);
				fw.write("EdgeColorMap:\t");
				for (int i = 0; i < j.matrixSize - 1; i++) {
					ArrayList<Edge> edges = j.getColorEdgesMap().get(
							(Integer) colors[i]);
					for (Edge e : edges)
						fw.write((Integer) colors[i] + ":" + e.getId() + "\t");
				}
				fw.write("\n");
				
				fw.write("ConnectedInterchange:\t");
				if (j.getConnectedInterchanges() != null)
					for (Interchange i : j.getConnectedInterchanges())
						fw.write(i.index + "\t");				
				fw.write("\n\n");

				MBTools.debug(j.getIndex() + "th junction write finished",
						false);
			}

			fw.flush();
			fw.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * calculate junction distributions
	 */
	@SuppressWarnings("unchecked")
	public void calJunctionDistributions(SGInterchange interchange) {
		for (Junction jn : interchange.getJunctions()) {
			jn.outgoingTripIds = (ArrayList<Integer>[]) new ArrayList<?>[jn.matrixSize];
			jn.incomingTripIds = (ArrayList<Integer>[]) new ArrayList<?>[jn.matrixSize];

			// create distribution for junctions if it does not exist
			if (jn.totalDistribution == null)
				jn.totalDistribution = new int[jn.matrixSize][jn.matrixSize][MBData.ONE_DAY_TIME];

			jn.distribution = new int[jn.matrixSize][jn.matrixSize][MBData.ONE_DAY_TIME];

			for (int i = 0; i < jn.matrixSize - 1; i++) {
				for (int j = 0; j < jn.matrixSize - 1; j++)
					calJunctionDistribution(jn, i + 1, j + 1);
			}

			for (int i = 0; i < jn.matrixSize - 1; i++) {
				calJunctionDistribution(jn, 0, i + 1);
				calJunctionDistribution(jn, i + 1, 0);
			}
		}

	}

	private void calJunctionDistribution(Junction jn, int from, int to) {
		if (from == to)
			return;

		int minSlice = 0;
		for (int startSlice = minSlice; startSlice < MBData.ONE_DAY_TIME; startSlice++) {
			float start = (startSlice) / (float) MBData.ONE_DAY_TIME;
			float end = (startSlice + 1) / (float) MBData.ONE_DAY_TIME;
			int common = 0;

			Object[] colors = jn.getColorEdgesMap().keySet().toArray();

			jn.incomingTripIds[from] = new ArrayList<Integer>();
			jn.outgoingTripIds[to] = new ArrayList<Integer>();

			if (from != 0) {
				ArrayList<Edge> edges = jn.getColorEdgesMap().get(
						(Integer) colors[from - 1]);
				for (Edge e : edges) {
					if (jn.getNodes().contains(e.getEndNode())) {
						jn.incomingTripIds[from] = (ArrayList<Integer>) ListOperation
								.symUnion(jn.incomingTripIds[from],
										e.getPassTripIds(start, end));
					}
				}
			}

			if (to != 0) {
				ArrayList<Edge> edges = jn.getColorEdgesMap().get(
						(Integer) colors[to - 1]);
				for (Edge e : edges) {
					if (jn.getNodes().contains(e.getStartNode())) {
						jn.outgoingTripIds[to] = (ArrayList<Integer>) ListOperation
								.symUnion(jn.outgoingTripIds[to],
										e.getPassTripIds(start, end));
					}
				}
			}

			int interchange = 0;
			if (from == 0) {
				for (int i = 1; i < jn.matrixSize; i++)
					interchange += jn.distribution[i][to][startSlice];
				common = jn.outgoingTripIds[to].size() - interchange;
			} else if (to == 0) {
				for (int i = 1; i < jn.matrixSize; i++)
					interchange += jn.distribution[from][i][startSlice];
				common = jn.incomingTripIds[from].size() - interchange;
			} else {
				ArrayList<Integer> commonTripsIds = (ArrayList<Integer>) ListOperation
						.intersection(jn.incomingTripIds[from],
								jn.outgoingTripIds[to]);
				common = commonTripsIds.size();
			}

			jn.distribution[from][to][startSlice] = common;
			jn.totalDistribution[from][to][startSlice] += common;
		}
	}

	public void writeJunctionDistributions(SGInterchange interchange) {
		try {
			FileWriter fw = new FileWriter(distributionFile);
			for (Junction jn : interchange.getJunctions()) {
				fw.write("index:\t" + jn.index);
				fw.write("\n");
				for (int i = 0; i < jn.matrixSize - 1; i++) {
					for (int j = 0; j < jn.matrixSize - 1; j++)
						writeDistribution(fw, jn, i + 1, j + 1);
				}

				for (int i = 0; i < jn.matrixSize - 1; i++) {
					writeDistribution(fw, jn, 0, i + 1);
					writeDistribution(fw, jn, i + 1, 0);
				}
				MBTools.debug("Junction node " + jn.index
						+ " distribution write finished with " + jn.matrixSize
						+ " circos size", false);
			}

			fw.flush();
			fw.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void writeDistribution(FileWriter fw, Junction jn, int from, int to)
			throws IOException {
		if (from == to)
			return;

		fw.write(from + ":" + to + "\t");

		int minSlice = 0;
		for (int startSlice = minSlice; startSlice < MBData.ONE_DAY_TIME; startSlice++) {
			fw.write(startSlice + ":"
					+ jn.totalDistribution[from][to][startSlice] + "\t");
		}

		fw.write("\n");
	}

	public String getDistributionFile() {
		return distributionFile;
	}
}
